#!/usr/bin/perl -w
# jmouriz@gmail.com
use strict;
use warnings;

use POE;
use POE::Component::Child;
use Curses::UI::POE;

use packet;

# Parameters
my $host = (shift or 'localhost');
my $port = (shift or 80);

# Config values
my $width = 60;
my $height = 15;

# UI

my $cui = new Curses::UI::POE(
  -color_support => 1,
  #-clear_on_exit => 1,
);

#my $language = new Curses::UI::Language 'spanish';
#$cui->lang($language);

# Key bindinds
$cui->set_binding(\&search, "\cS");
$cui->set_binding(\&help, "\cH");
$cui->set_binding(\&quit, "\cQ");

# Window
my $window = $cui->add(
  'window', 'Window',
  -fg => 'white',
  -bg => 'blue'
);

# Status label (bottom bar with shortcuts references)
my $status = $window->add(
  'status', 'Label',
  -text  => ' <Control>S: Buscar | <Control>H: Ayuda | <Control>Q: Salir',
  -width => -1,
  -y     => -1,
  -fg    => 'blue',
  -bg    => 'white',
);

# Left packets list
my $list = $window->add(
  'list', 'Listbox',
  -title      => 'Paquetes transmitidos',
  -border     => 1,
  -width      => $width,
  -padbottom  => 1,
  -vscrollbar => 1,
  -htmltext   => 1,
  -onchange   => \&select,
  -bfg        => 'white',
  -bbg        => 'blue',
);

# Right top headers view
my $head = $window->add(
  'head', 'TextViewer',
  -title    => 'Encabezados',
  -border   => 1,
  -x        => $width,
  -height   => $height,
  -bfg      => 'white',
  -bbg      => 'blue',
);

# Right bottom body view
my $data = $window->add(
  'data', 'TextViewer',
  -title     => 'Datos (ASCII)',
  -border    => 1,
  -x         => $width,
  -y         => $height,
  -padbottom => 1,
  -bfg       => 'white',
  -bbg       => 'blue',
);

# Focus on packets list
$list->focus();

# Global variables
my @packets;
my $packet = new packet;
my $state = 'none';
my $last = 0;

# Start background command in POE context
&run("ngrep -P '#' -N -e -l -d any -W byline host $host and port $port");

# Start UI
$cui->mainloop();

# Helper functions

sub run
{
  # POE handlers
  my %events = (
    stdout => 'parse',
    stderr => 'error',
  );
  
  # Create a POE session
  POE::Session->create(
    package_states => ['main' => [values(%events)]],
    inline_states  => {
      _start => sub { $_[KERNEL]->alias_set('main'); },
    }
  );
  
  # Create POE child context
  my $child = POE::Component::Child->new(events => \%events);

  # Run child
  $child->run(shift);
}

sub exit
{
    system 'ps -ef | grep ngrep | awk \'{ print $2 }\' | xargs kill';
    system 'reset';
    system 'clear';

    exit 0;
}

sub update
{
  for ($last .. $#packets)
  {
    $list->insert_at($last, $packets[$last++]->get_description);
  }

  $list->draw;
}

# Command handlers

sub error # process stderr
{
  my ($self, $args) = @_[ARG0 .. $#_]; # POE biolerplate
  print ">> $args->{out}";             # POE biolerplate
}

sub parse # process stdout
{
  my ($self, $args) = @_[ARG0 .. $#_]; # POE biolerplate
  $_ = $args->{out};                   # POE biolerplate

  chomp; #s/[\r\n]//g;
    
  # T: TCP
  # U: UDP
  #
  # U: URG: Urgent Pointer field significant.
  # A: ACK: Acknowledgment field significant.
  # P: PSH: Push Function.
  # R: RST: Reset the connection.
  # S: SYN: Synchronize sequence numbers.
  # F: FIN: No more data from sender.
  #
  # RFC0793: http://www.ietf.org/rfc/rfc0793.txt

  if (/^T\(\d+\) (\d+\.\d+\.\d+\.\d+):(\d+) -> (\d+\.\d+\.\d+\.\d+):(\d+) \[([UAPRSF]{1,2})\]/)
  {
    if ($packet->ready)
    {
      push @packets, $packet;
      &update;
      $packet = new packet;
    }

    if ('AP' ne $5)
    {
      #$state = 'none';
      goto end;
    }

    if ($port == $4)
    {
      $packet->set_description("|RQ|");
      $packet->set_description("$1:$2 ");
      $packet->set_description('=> ');
      $packet->set_description("$3:$4 ");

      $state = 'http-request';
    }
    else
    {
      $packet->set_description("|RS|");
      $packet->set_description("$3:$4 ");
      $packet->set_description('<= ');
      $packet->set_description("$1:$2 ");

      $state = 'http-response';
    }
  }
  else
  {
    $state = 'http-data' if $state eq 'http-head' and /^#$/;

    if ($state eq 'http-response' and /(HTTP)\/(\d+\.\d+) (\d+) (.+)#/)
    {
      $packet->set_description("$1/$2 ($3) $4");

      $state = 'http-head';
    }
    elsif ($state eq 'http-request' and /(GET|PUT|POST|HEAD) (.+) (HTTP)\/(\d+\.\d+)#/)
    {
      $packet->set_description("$1 $2 ($3/$4)");

      $state = 'http-head';
    }
    elsif ($state eq 'http-head' and /([^:]+): (.+)#/)
    {
      $packet->set_head("$1: $2\n");
    }
    elsif ($state eq 'http-data' and not /^#+$/)
    {
      $packet->set_data("$_\n");
    }
  }

  end:
}

# UI callbacks

sub help
{
  # TODO
}

sub search
{
  my $response = $cui->question(
    -question  => '    Ingrese la expresión regular que desea buscar',
    -title     => 'Buscar paquete', 
    -buttons   => [
      { -label => ' < Buscar > ', -shortcut => 'b', -value => 1 },
      { -label => ' < Cancelar > ', -shortcut => 'c', -value => 0 }
    ],
    -bfg       => 'black',
    -bbg       => 'cyan',
    -fg        => 'black',
    -bg        => 'cyan',
  );
}

sub quit
{
  my $response = $cui->dialog(
    -message   => '    Si cierra la aplicación perderán todos los paquetes capturados',
    -title     => '¿Está seguro?', 
    -buttons   => [
      { -label => ' < Sí > ', -shortcut => 's', -value => 1 },
      { -label => ' < No > ', -shortcut => 'n', -value => 0 }
    ],
    -bfg       => 'black',
    -bbg       => 'white',
    -fg        => 'black',
    -bg        => 'white',
  );

  &exit if $response;
}

sub select
{
  my $listbox = shift;

  my $head = $listbox->parent->getobj('head');
  my $data = $listbox->parent->getobj('data');

  my $packet = $packets[$listbox->id];

  $head->text($packet->get_head);
  $data->text($packet->get_data);

  #if ($packet)
  #{
  #  $head->text($packet->get_head);
  #  $data->text($packet->get_data);
  #}
  #else
  #{
  #  $head->text('- sin datos -');
  #  $data->text('- sin datos -');
  #}

  $head->draw;
  $data->draw;
}

